namespace mini {

    export type EaseFun = (t: number) => number;

    export abstract class Action { // 基类

        public next: Action = null;
        public prev: Action = null;
        public ease: EaseFun = null;

        protected inited = false;
        readonly target: any;

        readonly startTime: number; // 时间线的时间
        readonly endTime: number; // 时间线的时间
        readonly duration: number;

        constructor(target: any, startTime: number, duration: number) {
            this.target = target;
            this.startTime = startTime;
            this.duration = duration;
            this.endTime = startTime + duration;

            this.setPosition = this.setPosition;// hot function
            this.update = this.update;
        }

        public setPosition(position: number, isReverse: boolean) { // 时间线的时间
            let ratio = this.duration === 0 ? 1 : (position - this.startTime) / this.duration;
            if (ratio < 0) {
                ratio = 0;
            } else if (ratio > 1) {
                ratio = 1;
            }
            if (this.ease) {
                ratio = this.ease(ratio);
            }
            if (!this.inited) {
                this.inited = true;
                this.init(ratio, isReverse);
            }
            this.update(ratio, isReverse);
        }

        protected init(ratio: number, isReverse: boolean) {
            // override me if need
        }

        protected abstract update(ratio: number, isReverse: boolean);
    };

    export class RelativeNum extends Action {

        private lastDeltaValue = 0;
        constructor(target: any, startTime: number, duration: number, private deltaValue: number, private propName: string) {
            super(target, startTime, duration);
        }

        protected update(ratio: number, isReverse: boolean) {
            let currDeltaValue = ratio * this.deltaValue;
            let delta = currDeltaValue - this.lastDeltaValue;
            this.lastDeltaValue = currDeltaValue;
            this.target[this.propName] += delta;
        }
    }
    export class AbsoluteNum extends Action {

        private startValue = 0;
        private deltaValue = 0;

        constructor(target: any, startTime: number, duration: number, private toValue: number, private propName: string) {
            super(target, startTime, duration);
        }

        protected init(ratio: number, isReverse: boolean) {
            this.startValue = this.target[this.propName];
            this.deltaValue = this.toValue - this.startValue;
        }

        protected update(ratio: number, isReverse: boolean) {
            this.target[this.propName] = this.startValue + ratio * this.deltaValue;
        }
    }

    export class StepAction extends Action {

        private stepIndex = 0;
        private stepLength: number;
        constructor(target: any, startTime: number, duration: number, private steps: any[], private propName: string) {
            super(target, startTime, duration);
            if (!steps || !steps.length) {
                throw "steps 没有数据!!!";
            }
            this.stepLength = steps.length;
        }

        protected update(ratio: number, isReverse: boolean) {
            if (ratio < 0) {
                ratio = 0;
            } else if (ratio > 1) {
                ratio = 1;
            }
            let stepIndex = Math.ceil(ratio * this.stepLength) - 1;
            if (this.stepIndex === stepIndex) {
                return;
            }
            this.stepIndex = stepIndex;
            this.target[this.propName] = this.steps[stepIndex];
        }
    }

    export class SetupAction extends Action {

        private startValue: any;

        constructor(target: any, startTime: number, duration: number, readonly toValue: any, readonly propName: string) {
            super(target, startTime, 0.0001);
        }

        protected init(ratio: number, isReverse: boolean) {
            this.startValue = this.target[this.propName];
        }

        protected update(ratio: number, isReverse: boolean) {
            if (ratio <= 0) {
                this.target[this.propName] = this.startValue;
            } else if (ratio >= 1) {
                this.target[this.propName] = this.toValue;
            }
        }
    }

    export enum ActionType { // maybe faster use number
        RelNum = 'RelativeNum',
        AbsNum = 'AbsoluteNum',
        Setup = 'SetupAction',
        Step = 'StepAction',
    }
    export const ActionConstructorMap = {
        [ActionType.RelNum]: RelativeNum,
        [ActionType.AbsNum]: AbsoluteNum,
        [ActionType.Setup]: SetupAction,
        [ActionType.Step]: StepAction,
    };

    export interface ActionData {
        t: number; dur: number; type: ActionType; v: any; propName: string,
        ease?: EaseFun;
    }
    export interface TweenOptions {
        loop?: number,
        useTicks?: boolean,
        yoyo?: boolean,
        timeScale?: number,
    }

    export class Tween {
        public static readonly Complete = 'complete';
        public static isInTick = 0;

        static tick(delta: number, paused: boolean, tween: Tween) { // delta 可正可负
            var t = Tween.isInTick = Date.now();

            if (tween) {
                tween.lastTick = t;
                tween.advance(tween.useTicks ? 1 : delta);
            }
            Tween.isInTick = 0;
        };

        readonly loop: number;
        readonly useTicks: boolean;
        readonly yoyo: boolean;
        public timeScale: number;
        public target: any;

        private actionHead: Action = null;
        private actionTail: Action = null;

        private prevTime = 0;
        readonly duration: number;

        private rawPosition = 0;
        public lastTick = 0;

        private _paused: boolean = false;
        set paused(value) {
            this._paused = value;
        }
        get paused() {
            return this._paused;
        }

        constructor(target: any, frames: ActionData[], options?: TweenOptions) {
            this.target = target;

            this.loop = 0;
            this.useTicks = false;
            this.yoyo = false;
            this.timeScale = 1;

            this.rawPosition = 0;
            this.lastTick = 0;

            if (options) {
                this.loop = options.loop < 0 ? -1 : (options.loop || 0);
                this.useTicks = !!options.useTicks;
                this.yoyo = !!options.yoyo;
                this.timeScale = options.timeScale || 1;
            }
            if (!frames || frames.length === 0) {
                throw "frames 没有数据!!!";
            }
            this.duration = this.initActions(frames);
        }

        private initActions(frames: ActionData[]) {
            frames.sort((a, b) => {
                return a.t - b.t;
            });

            let duration: number = 0;
            for (const frame of frames) {
                let ClassContr: any = ActionConstructorMap[frame.type];
                if (!ClassContr) {
                    continue;
                }
                let action: Action = new ClassContr(this.target, frame.t, frame.dur, frame.v, frame.propName);
                action.ease = frame.ease || null;
                let actionTail = this.actionTail;
                if (!actionTail) {
                    this.actionHead = this.actionTail = action;
                } else {
                    this.actionTail = actionTail.next = action;
                    action.prev = actionTail;
                }
                if (action.endTime > duration) {
                    duration = action.endTime;
                }
            }
            return duration;
        }

        public advance(delta: number) {// delta 可正可负
            this.setPosition(this.rawPosition + delta * this.timeScale);
        };

        public setPosition(rawPosition: number) {
            const d = this.duration, loopCount = this.loop, prevRawPos = this.rawPosition;
            let loop = 0, position = 0, end = false;

            if (d === 0) {// deal with 0 length tweens.

                let action = this.actionHead;
                let next: Action;
                while (action) {
                    next = action.next;
                    action.setPosition(position, false);
                    action = next;
                }
                this.paused = true;
                this.dispatchEvent(Tween.Complete);
                return;
            } else {

                loop = rawPosition / d | 0;// 向下取整
                position = rawPosition - loop * d;

                end = (loopCount !== -1 && rawPosition >= loopCount * d + d);
                if (end) {
                    rawPosition = (position = d) * (loop = loopCount) + d;
                }
                if (rawPosition === prevRawPos) {
                    return;// no need to update
                }

                if (this.yoyo && (loop % 2 !== 0)) {// current loop is reversed
                    position = d - position;
                }
            }
            this.rawPosition = rawPosition;

            const prevTime = this.prevTime;
            this.prevTime = position;
            if (prevTime === position) {
                return;
            }

            if (position > prevTime) {

                let action = this.actionHead;
                let next: Action;
                while (action) {
                    next = action.next;
                    if (action.endTime < prevTime || action.startTime > position) {
                        action = next;
                        continue;
                    }
                    action.setPosition(position, false);
                    action = next;
                }
            } else {// reverse

                let action = this.actionTail;
                let prev: Action;
                while (action) {
                    prev = action.prev;
                    if (action.endTime < position || action.startTime > prevTime) {
                        action = prev;
                        continue;
                    }
                    action.setPosition(position, true);
                    action = prev;
                }
            }

            if (end) {
                this.paused = true;
                this.dispatchEvent(Tween.Complete);
            }

        }

        public dispatchEvent(eventName: string) {

        }
    }

}